using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Rhino.Mocks;

namespace MyTest
{
    internal class TestConstruct<TInput, TOuptut> : ITestConstruct<TInput, TOuptut>
    {
        private Func<TInput, TOuptut> _functionUnderTest;
        private TInput _input;
        private bool hasAssertion;

        public ITestConstruct<TInput, TOuptut> WhenICall(Func<TInput, TOuptut> functionUnderTest)
        {
            _functionUnderTest = functionUnderTest;
            return this;
        }
        public ITestConstruct<TInput, TOuptut> With(TInput input)
        {
            _input = input;
            return this;
        }

        public void ExpectThatICall<TDependency>(TDependency mock, Action<TDependency> action)
            where TDependency : class
        {
            if (hasAssertion)
                throw new InvalidTestSetupException("You cannot set up more than one assertion/verification on a test");
            hasAssertion = true;
            Expectation<TDependency>.RunExpectation(mock, action, () => _functionUnderTest(_input));
        }

        public void ExpectThatICall<TDependency, TDependencyOutput>(TDependency mock, Function<TDependency, TDependencyOutput> expectedFunction, TDependencyOutput dependencyOutput) where TDependency : class
        {
            if (hasAssertion)
                throw new InvalidTestSetupException("You cannot set up more than one assertion/verification on a test");
            hasAssertion = true;
            Expectation<TDependency>.RunExpectation(mock, expectedFunction, dependencyOutput, () => _functionUnderTest(_input));
        }

        public void ExpectThatINeverCall<TDependency>(TDependency mock, Action<TDependency> action) where TDependency : class
        {
            if (hasAssertion)
                throw new InvalidTestSetupException("You cannot set up more than one assertion/verification on a test");
            hasAssertion = true;
            Expectation<TDependency>.RunNeverExpectation(mock, action, () => _functionUnderTest(_input));
        }

        public void AssertOutputIsSatifiedBy(Func<TOuptut, bool> predicate)
        {
            if (hasAssertion)
                throw new InvalidTestSetupException("You cannot set up more than one assertion/verification on a test");
            var output = _functionUnderTest(_input);
            Assert.IsTrue(predicate(output));
            hasAssertion = true;
        }

        public void AnExceptionIsExpected<T>() where T : Exception
        {
            if (hasAssertion)
                throw new InvalidTestSetupException("You cannot set up more than one assertion/verification on a test");
            try
            {
                _functionUnderTest(_input);
            }
            catch(Exception e)
            {
                if (typeof(T) == e.GetType())//catch(T) not working?!??!
                {
                    //this is a pass; i wanted this expection to be raised
                    hasAssertion = true;
                    return;
                }
                Assert.Fail("{0} expected, but an exception of {1} was thrown instead", typeof(T).FullName, e.GetType().FullName);
            }
            Assert.Fail("{0} expected", typeof(T).FullName);
        }

        internal static class Expectation<TDependency> where TDependency : class
        {
            internal static void RunNeverExpectation(TDependency mock, Action<TDependency> expectedAction, Action runTest)
            {
                mock.Expect(expectedAction).Repeat.Never();
                runTest();
                mock.VerifyAllExpectations();
            }
            internal static void RunExpectation(TDependency mock, Action<TDependency> expectedAction, Action runTest)
            {
                mock.Expect(expectedAction);
                runTest();
                mock.VerifyAllExpectations();
            }
            internal static void RunExpectation<TResult>(TDependency mock, Function<TDependency, TResult> expectedFunction, TResult output, Action runTest)
            {
                mock.Expect(expectedFunction).Return(output);
                runTest();
                mock.VerifyAllExpectations();
            }
        }
    }
}